package service.treemanager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import service.configuration.IConfiguration;
import service.configuration.NamingUtil;
import service.sheetmanager.ISheetManager;
import dao.IDaoAsof;
import entities.Sheet;
import entities.TreeAssociation;
import web.form.SheetInfo;
import web.form.SheetType;
import web.form.SheetView;


@Service
public class TreeManager implements ITreeManager {

	@Resource
	private IDaoAsof dao;
	
	@Resource
	private IConfiguration config;
	
	@Resource
	private ISheetManager sheetManager;
	
	private final RootTreeAssociationCodeGetter rootCodeGetter;
	private final ChildTreeAssociationCodeGetter childCodeGetter;
	
	private List<String> rootTypes;
	private List<SheetType> rootTypeViews;
	private Map<String, List<String>> rootsByType;
	private boolean generatingTree;
	
	private String rootName;
	
	public TreeManager() {
		rootCodeGetter = new RootTreeAssociationCodeGetter();
		childCodeGetter = new ChildTreeAssociationCodeGetter();
		
		this.generatingTree = false;
		rootTypes = new ArrayList<String>();
		rootsByType = new HashMap<String, List<String>>();
	}
	
	private boolean codeExist(String code) {
		for(String type : config.getSheetTypes()) {
			if (dao.findSheetByCode(code, type) != null)
				return true;
		}
		return false;
	}
	
	/**
	 * Génère l'arborescence et la stocke dans un cache.
	 * Cette méthode est déclenché de façon asynchrone à la création de service
	 */
	@Async
	public void generateTree() {
		this.generatingTree = true;
		long start = System.currentTimeMillis();
		System.out.println("ASOF : Génération de l'arborescence en cours...");
		List<String> rootTypes = dao.findRootTypes();
		List<SheetType> rootTypeViews = generateSheetType(rootTypes);
		Map<String, List<String>> rootsByType = new HashMap<String, List<String>>();
		System.out.println("ASOF : Types racine déterminés en "
				+ (System.currentTimeMillis() - start) +
				"ms ("+ rootTypes + ")");
		for(String type : rootTypes) {
			long loopStart = System.currentTimeMillis();
			rootsByType.put(
					type, 
					parseCodes(
							dao.findRootTreeAssociationsByType(type), 
							rootCodeGetter
					)
			);
			System.out.println("ASOF : Fiches filles de type " + type + " déterminés en "
					+ (System.currentTimeMillis() - loopStart) +
					"ms");
		}
		System.out.println("ASOF : Fin génération arborescence.");
		
		String rootStartName = "ro";
		String rootMiddleName = "";
		String rootEndName = "ot";
		while (codeExist(rootStartName+rootMiddleName+rootEndName)) {
			rootMiddleName += "o";
		}
		this.rootName = rootStartName+rootMiddleName+rootEndName;
		
		this.rootTypes = rootTypes;
		this.rootTypeViews = rootTypeViews;
		this.rootsByType = rootsByType;
		this.generatingTree = false;
	}
	
	
	/**
	 * @return true si l'arbre est en court de génération, false sinon
	 */
	public boolean isGeneratingTree() {
		return generatingTree;
	}
	
	/**
	 * Code de la fiche racine
	 * @return chaine de caratère
	 */
	public String getRootName() {
		return rootName;
	}
	
	/**
	 * Récupèrer tous les types des fils d'une fiche donnée
	 * @param sheetCode code d'une fiche
	 * @return liste de type sous forme de vue
	 */
	public List<SheetType> getChildrenTypes(String sheetCode) {
		return generateSheetType(dao.findChildrenTypes(sheetCode));
	}

	/**
	 * Récupèrer toutes les fiches filles d'une fiche donnée et d'un type donné
	 * @param sheetCode code d'une fiche
	 * @param childType type de fiche
	 * @return liste de fiches sous forme de vue courte
	 */
	public List<SheetInfo> getChildren(String sheetCode, String childType) {
		return  findSheetInfoByCodes(dao.findTreeAssociations(sheetCode, childType), childCodeGetter, childType);
	}

	/**
	 * Récupèrer les types racines.
	 * @return liste des types racines
	 */
	public List<String> getRootTypes() {
		return rootTypes;
	}
	
	/**
	 * Récupèrer les types racines sous forme de vue.
	 * @return la liste des types racines sous forme de vue
	 */
	public List<SheetType> getRootTypeViews() {
		return rootTypeViews;
	}
	
	/**
	 * Récupèrer les fiches racines d'un type donné en argument
	 * sous forme de vue courte.
	 * @param sheetType type des fiches recherché
	 * @return la liste des fiches sous forme de vue courte
	 */
	public List<SheetInfo> getRootsByType(String type) {
		return findSheetInfoByCodes(rootsByType.get(type), type);
	}

	/**
	 * Récupèrer les fiches filles d'une fiche donnée et pour un type de fils donné,
	 * sous forme de vue
	 * @param sheetCode code de la fiche parent
	 * @param childType  type des fils souhaités
	 * @return la liste des fiches sous forme de vue
	 */
	public List<SheetView> getChildrenAsSheetViews(String sheetCode, String childType) {
		return  findSheetViewByCodes(dao.findTreeAssociations(sheetCode, childType), childCodeGetter, childType);
	}
	
	
	/**
	 * Récupèrer les fiches racines d'un type donné en argument
	 * sous forme de vue.
	 * @param sheetType type des fiches recherché
	 * @return la liste des fiches sous forme de vue
	 */
	public List<SheetView> getRootsByTypeAsSheetViews(String type) {
		return  findSheetViewByCodes(dao.findRootTreeAssociationsByType(type), rootCodeGetter, type);
	}
	
	/**
	 * Récupèrer les codes des fiches filles d'une fiche donnée.
	 * @param sheetCode code de la fiche parent
	 * @param childType  type des fils souhaités
	 * @return la liste des codes
	 */
	public List<String> getChildrenCodes(String sheetCode, String childType) {
		List<TreeAssociation> associations = dao.findTreeAssociations(sheetCode, childType);
		
		List<String> childrenCodes = new ArrayList<String>();
		for(TreeAssociation association : associations) {
			childrenCodes.add(association.getChildCode());
		}
		
		return childrenCodes;
	}
	
	private List<SheetType> generateSheetType(List<String> types) {
		List<SheetType> typeViews = new ArrayList<SheetType>(types.size());
		for (String type: types) {
			typeViews.add(new SheetType(type, config.getSheetConfig(type).getLabel()));
		}
		return typeViews;
	}
	
	
	
	/**
	 * @param <T> Class des object traité
	 */
	private interface CodeGetter<T>{
		String getCode(T o);
	}
	
	/**
	 * Permet de retrouver le code du père pour TreeAssociation donnée
	 */
	public class RootTreeAssociationCodeGetter implements CodeGetter<TreeAssociation> {
		public String getCode(TreeAssociation treeAssociation) {
			return treeAssociation.getParentCode();
		}
	}
	
	/**
	 * Permet de retrouver le code du fils pour TreeAssociation donnée
	 */
	public class ChildTreeAssociationCodeGetter implements CodeGetter<TreeAssociation> {
		public String getCode(TreeAssociation treeAssociation) {
			return treeAssociation.getChildCode();
		}
	}
	
	
	/**
	 * Récupèrer les codes d'une liste d'objets.
	 * @param object liste des objets à traiter
	 * @param codeGetter outil permettant de retrouver le bon code
	 * @return liste des codes des objets traités
	 */
	private <T> List<String> parseCodes(List<T> object, CodeGetter<T> codeGetter) {
		
		List<String> codes = new ArrayList<String>();
		
		for(T o : object) {
			codes.add(codeGetter.getCode(o));
		}
		
		return  codes;
	}
	
	/**
	 * Récupèrer les fiches (en affichage court) 
	 * grâce au donnée d'une liste d'objet, d'un récupérateur de code 
	 * et d'un type de fiche.
	 * @param object liste d'objets à traiter
	 * @param codeGetter outil permettant de retrouver le code
	 * @param type de fiche
	 * @return liste des fiches récupérées (en affichage court)
	 */
	private <T> List<SheetInfo> findSheetInfoByCodes(List<T> object, 
			CodeGetter<T> codeGetter, String type) {
		return  findSheetInfoByCodes(parseCodes(object, codeGetter), type);
	}
	
	/**
	 * Récupèrer les fiches (en affichage court) 
	 * @param codes codes codes des fiches à réupérer
	 * @param type des fiches à réupérer
	 * @returnliste des fiches récupérées (en affichage court)
	 */
	private <T> List<SheetInfo> findSheetInfoByCodes(List<String> codes, String type) {
		
		List<SheetInfo> result = new ArrayList<SheetInfo>();
		
		for (Sheet sheet : dao.findSheetsByProperty(NamingUtil.CODE, codes, type)) {
			result.add(sheetManager.generateSheetInfo(sheet));
		}
		
		return  result;
	}
	
	
	/**
	 * Récupèrer les fiches sous forme de vue
	 * @param object liste d'objets à traiter
	 * @param codeGetter outil permettant de retrouver le code
	 * @param type type des fiches à récupérer
	 * @return liste des fiches récupérer sous forme de vue
	 */
	private <T> List<SheetView> findSheetViewByCodes(List<T> object, 
			CodeGetter<T> codeGetter, String type) {
		return  findSheetViewByCodes(parseCodes(object, codeGetter), type);
	}
	
	/**
	 * Récupèrer les fiches sous forme de vue
	 * @param codes codes des fiches à récupérer
	 * @param type type des fiches à récupérer
	 * @return liste des fiches récupérer sous forme de vue
	 */
	private <T> List<SheetView> findSheetViewByCodes(List<String> codes, String type) {
		
		List<SheetView> result = new ArrayList<SheetView>();
		
		for (Sheet sheet : dao.findSheetsByProperty(NamingUtil.CODE, codes, type)) {
			result.add(sheetManager.generateSheetView(sheet));
		}
		
		return  result;
	}


	
//	private List<SheetInfo> findSheetInfoByCodes(List<String> codes, String type) {
//		
//		List<SheetInfo> result = new ArrayList<SheetInfo>();
//		
//		for (Sheet sheet : dao.findSheetsByProperty(NamingUtil.CODE, codes, type)) {
//			result.add(sheetManager.generateSheetInfo(sheet));
//		}
//		
//		return  result;
//	}	
//	
//	
//	public List<SheetInfo> getChildren(String sheetCode, String childType) {	
//		List<String> codes = new ArrayList<String>();
//		
//		for(TreeAssociation asso : dao.findTreeAssociations(sheetCode, childType)) {
//			codes.add(asso.getChildCode());
//		}
//
//		return findSheetInfoByCodes(codes, childType);
//	}
//	
//	public List<SheetInfo> getRootsByType(String type) {
//		List<String> codes = new ArrayList<String>();
//		
//		for(TreeAssociation asso : dao.findRootsByType(type)) {
//			codes.add(asso.getChildCode());
//		}
//		
//		return findSheetInfoByCodes(codes, type);
//	}
}
